/*
 * Ken Anderson, 2010
 */

#include "Renderer.h"
#include "util.h"
#include <math.h>
#include <algorithm>

#define MIN_HEURISTIC_RANGE 50.0f

Renderer::Renderer( WorldState * worldState )
{ 
	mWorldState = worldState;
	mFont = NULL;
}

Renderer::~Renderer()
{
	if( mFont )
		delete mFont;
	glDeleteLists( mNodeGeometryIndex, 1 );
}

void Renderer::Initialize( Graph * graph )
{
	// Assume that the graph does not change!
	// Set the bounding box for the whole graph
	for( unsigned int i=0; i<graph->GetNumOfNodes(); i++)
	{
		Node * node = graph->GetNode(i);
		if( mGraphBoundingBox.maxX < node->GetX() + BOUNDING_BOX_BUFFER )
			mGraphBoundingBox.maxX = (float)(node->GetX() + BOUNDING_BOX_BUFFER);
		if( mGraphBoundingBox.maxY < node->GetY() + BOUNDING_BOX_BUFFER )
			mGraphBoundingBox.maxY = (float)(node->GetY() + BOUNDING_BOX_BUFFER);
		if( mGraphBoundingBox.minX > node->GetX() - BOUNDING_BOX_BUFFER )
			mGraphBoundingBox.minX = (float)(node->GetX() - BOUNDING_BOX_BUFFER);
		if( mGraphBoundingBox.minY > node->GetY() - BOUNDING_BOX_BUFFER )
			mGraphBoundingBox.minY = (float)(node->GetY() - BOUNDING_BOX_BUFFER);
	}

	// Initialize the viewpoint distance, dependent on the graph bounding box
	mWorldState->zDistance = (GLfloat) -1.0 * std::max( 
		(GLfloat) (mGraphBoundingBox.height() / (2.0*sin((FOV/2.0)*PI/180.0))),
		(GLfloat) (mGraphBoundingBox.width()  / (2.0*sin((FOV/2.0)*PI/180.0)))
		);

	InitializeOpenGL();
	InitializeText();
}

void Renderer::InitializeOpenGL()
{
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glEnable(GL_BLEND);	
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);	

	// Initialze OpenGL perspective matrix
	SetPerspective();

	// Initialize objects in video card cache
	mNodeGeometryIndex = glGenLists(1);
	glNewList ( mNodeGeometryIndex, GL_COMPILE );
		glBegin(GL_POLYGON);
			glVertex3f( -0.45f, -0.45f, 0.0f );
			glVertex3f(  0.45f, -0.45f, 0.0f );
			glVertex3f(  0.45f,  0.45f, 0.0f );
			glVertex3f( -0.45f,  0.45f, 0.0f );
		glEnd();
    glEndList ();

	if(NORMAL) printf("Initialized OpenGL\n");
}

void Renderer::InitializeText( const char * fontFileName )
{
	const char* file = fontFileName;
	mFont = new FTExtrudeFont( file );
	if(mFont->Error())
    {
        if(FATAL) fprintf(stderr, "Could not load font `%s'\n", file);
        exit(1);
    }
	mFont->FaceSize(32);
}

void Renderer::SetPerspective()
{
	glViewport( 0, 0, mWorldState->mWindowWidth, mWorldState->mWindowHeight );
	glMatrixMode( GL_PROJECTION );
	glEnable( GL_DEPTH_TEST );
	glLoadIdentity(); 
	GLdouble zFar = 2.0 * (mWorldState->mWindowHeight + mWorldState->mWindowWidth );
	gluPerspective( 45, (float)mWorldState->mWindowWidth / (float)mWorldState->mWindowHeight, 0.1, zFar );
	glMatrixMode( GL_MODELVIEW );
}

void Renderer::SetColor( GLfloat value, GLfloat minValue, GLfloat maxValue, Color color )
{
	// between 0.0 and 1.0
	GLfloat normalizedValue = (GLfloat) std::max(std::min( (value-minValue)/(maxValue-minValue), 1.0f ),0.0f);
	// between 0.0 and MAX_WHITENESS
	normalizedValue *= (GLfloat) MAX_WHITENESS;
	GLfloat red = (GLfloat) MAX_WHITENESS - normalizedValue;
	GLfloat green = (GLfloat) MAX_WHITENESS - normalizedValue;
	GLfloat blue = (GLfloat) MAX_WHITENESS - normalizedValue;
	
	switch(color)
	{
		case RED:
			red = (GLfloat) MAX_WHITENESS;
			break;
		case GREEN:
			green = (GLfloat) MAX_WHITENESS;
			break;
		case BLUE:
			blue = (GLfloat) MAX_WHITENESS;
			break;
		default:
			break;
	}

	glColor3f( red, green, blue );
}

// Please note that all draw functions take into account the BoundingBox offset!!
void Renderer::DrawCoordinate(GLfloat x, GLfloat y, GLfloat z )
{
	// New method
	glPushMatrix();
		glTranslatef( x-mGraphBoundingBox.centerX(), y-mGraphBoundingBox.centerY(), z );
		glCallList (mNodeGeometryIndex);
	glPopMatrix();
}


// Please note that all draw functions take into account the BoundingBox offset!!
void Renderer::DrawEdge(GLfloat startX, GLfloat startY, GLfloat endX, GLfloat endY, GLfloat z)
{
	// Draw Line
	glBegin(GL_LINES);
	glVertex3f( 
		startX-mGraphBoundingBox.centerX(), 
		startY-mGraphBoundingBox.centerY(), 
		z 
		);
	glVertex3f( 
		endX-mGraphBoundingBox.centerX(), 
		endY-mGraphBoundingBox.centerY(), 
		z 
		);
	glEnd( );

	// Draw Arrow
	glPushMatrix();
		glTranslatef( endX-mGraphBoundingBox.centerX(), endY-mGraphBoundingBox.centerY(), 0.0f );
		GLfloat angle = atan2((endY-startY),(endX-startX));
		glRotatef( angle*180.0f/PI, 0.0f, 0.0f, 1.0f );
		glBegin(GL_TRIANGLES);
			glVertex3f(  0.00f,  0.00f, z );
			glVertex3f( -0.35f,  0.15f, z );
			glVertex3f( -0.35f, -0.15f, z );
		glEnd();
	glPopMatrix();
}

void Renderer::PreDraw()
{
	// Clear frame and set the camera angle
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	glTranslatef( mWorldState->xOffset, mWorldState->yOffset, mWorldState->zDistance );
	glRotatef(mWorldState->mYRotationAngle, 0, 1, 0);
	glRotatef(mWorldState->mXRotationAngle, 1, 0, 0);
}

void Renderer::PostDraw()
{
	SDL_GL_SwapBuffers();
}

void Renderer::Render(Graph * graph)
{
	// error
	if( !graph )
		return;
	
	PreDraw();
	DrawNodes( graph, 0.0f );
	DrawEdges( graph, -0.1f );
	Draw2DText();
	PostDraw();
}

void Renderer::Render(Heuristic * heuristic)
{
	// error
	if( !heuristic )
		return;
	
	PreDraw();
	DrawNodes( heuristic, 0.0f );
	DrawEdges( heuristic->GetGraph(), -0.2f );
	Draw2DText();
	PostDraw();
}

void Renderer::Render(Search * search)
{
	// error
	if( !search )
		return;

	PreDraw();

	// Draw normal search
	if( mWorldState->mShowLevel0 )
	{
		DrawNodes( search, 0.0f );
		DrawEdges( search->GetGraph(), -0.2f );
		DrawSolutionPath( search, 0.2f );
	}

	// Draw Level two (if it is our abstract search)
	ResumableAdditiveHeuristic * heuristicWithAbstractSearch = dynamic_cast<ResumableAdditiveHeuristic*>(search->GetHeuristic());
	if( mWorldState->mShowLevel1 )
	{
		if( heuristicWithAbstractSearch )
		{
			Search * abstractSearch = heuristicWithAbstractSearch->GetXHeuristic()->GetSearch();
			DrawNodes( abstractSearch, SPACE_BETWEEN_LEVEL );
			DrawEdges( abstractSearch->GetGraph(), SPACE_BETWEEN_LEVEL - 0.1f );
		}
	}

	// Draw Level two (if it is our abstract search)
	if( mWorldState->mShowLevel2 )
	{
		if( heuristicWithAbstractSearch )
		{
			Search * abstractSearch = heuristicWithAbstractSearch->GetYHeuristic()->GetSearch();
			DrawNodes( abstractSearch, 2*SPACE_BETWEEN_LEVEL );
			DrawEdges( abstractSearch->GetGraph(), 2*SPACE_BETWEEN_LEVEL - 0.1f );
		}
	}

	// Text
	Draw2DText( search );

	PostDraw();
}

void Renderer::DrawNodes( Graph * graph, GLfloat z )
{
	glColor3f( (GLfloat) 0.7, (GLfloat) 0.5, (GLfloat) 0.8 );
	Node * node;
	for(unsigned int i=0; i<graph->GetNumOfNodes(); i++)
	{
		node = graph->GetNode(i);
		DrawCoordinate( node->GetX(), node->GetY(), z );
	}
}

void Renderer::DrawNodes( Heuristic * heuristic, GLfloat z)
{
	float minimum;
	float maximum;
	if( COLOR_HEURISTIC ) {
		minimum = 0.0f;
		maximum= std::max(heuristic->GetMaxValue(), minimum+MIN_HEURISTIC_RANGE);
	}
	for(unsigned int i=0; i<heuristic->GetGraph()->GetNumOfNodes(); i++)
	{
		Node * node = heuristic->GetGraph()->GetNode(i);
		if( COLOR_HEURISTIC ) {
			SetColor( heuristic->ShowValue(node), minimum, maximum, RED );
		}
		else
			glColor3f( (GLfloat) 1.0, (GLfloat) 0.5, (GLfloat) 0.5 );
		DrawCoordinate( node->GetX(), node->GetY(), z );
	}
}

void Renderer::DrawNodes(Search * search, GLfloat z )
{
	float minHeuristic, maxHeuristic;
	
	// Calculate maximums
	if( COLOR_HEURISTIC ) {
		minHeuristic = 0.0f;
		maxHeuristic = std::max(search->GetHeuristic()->GetMaxValue(), minHeuristic+MIN_HEURISTIC_RANGE);
	}

	// Set colors
	// Different colors determined by whether the node has been expanded or not.
	for(unsigned int i=0; i<search->GetGraph()->GetNumOfNodes(); i++)
	{
		Node * node = search->GetGraph()->GetNode(i);
		if( search->IsClosed(node) )
		{
			if( mColoringMethod == COLOR_F_VALUE )
			{
				float minimum = search->GetHeuristic()->ShowValue(search->GetStart());
				float maximum = std::max(search->GetGVal(search->GetGoal()), minimum+MIN_HEURISTIC_RANGE);
				SetColor( search->GetGVal(node)+search->GetHeuristic()->ShowValue(node),
					minimum, maximum, BLUE );
			}
			else if( mColoringMethod == COLOR_NUM_EXPANSIONS ) {
				SetColor( (GLfloat)search->GetExpansionCount(node), 0.0f, 10.0f, BLUE );
			}
			else if( mColoringMethod == COLOR_G_VALUE )
			{
				float minimum = 0.0f;
				float maximum = std::max(search->GetGVal(search->GetGoal()), minimum+MIN_HEURISTIC_RANGE);
				SetColor( search->GetGVal(node),minimum, maximum, BLUE );
			}
			else
				glColor3f( (GLfloat) 0.5, (GLfloat) 0.5, (GLfloat) 0.5 );
		}
		// Untouched node
		else {
			if( COLOR_HEURISTIC ) {
				SetColor( search->GetHeuristic()->ShowValue(node), minHeuristic, maxHeuristic, RED );
			}
			else
				glColor3f( (GLfloat) 1.0, (GLfloat) 0.5, (GLfloat) 0.5 );
		}

		DrawCoordinate( node->GetX(), node->GetY(), z );
	}
}

void Renderer::DrawSolutionPath( Search * search, GLfloat z )
{
	glColor3f( (GLfloat) 0.5, (GLfloat) 0.2, (GLfloat) 0.9 );
	glLineWidth(4.0f);
	// Draw the solution
	unsigned int solutionLength = search->MinimalPathLength();
	for(unsigned int i=0; i<solutionLength; i++)
	{
		Edge* edge = search->MinimalPathEdge( i );
		DrawEdge( 
			edge->FromNode()->GetX(), edge->FromNode()->GetY(),
			edge->ToNode()->GetX(), edge->ToNode()->GetY(),
			z
			);
	}
}

void Renderer::DrawEdges ( Graph * graph, GLfloat z )
{
	// error
	if( !graph )
		return;

	glColor3f( (GLfloat) 0.7, (GLfloat) 0.7, (GLfloat) 0.7 );
	glLineWidth(4.0f);
	Node * node;
	Edge * edge;
	for(unsigned int i=0; i<graph->GetNumOfNodes(); i++)
	{
		node = graph->GetNode(i);
		for(unsigned int j=0; j<node->NumOutEdges(); j++)
		{
			edge = node->GetOutEdge(j);
			DrawEdge( 
				edge->FromNode()->GetX(), edge->FromNode()->GetY(),
				edge->ToNode()->GetX(), edge->ToNode()->GetY(),
				z
				);
		}
	}
}

// Search can be NULL
void Renderer::Draw2DText( Search * search )
{
	if( mFont )
	{
		static char string[100];
		glDisable(GL_DEPTH_TEST);
		glMatrixMode (GL_PROJECTION);
		glPushMatrix();
			glLoadIdentity (); 
			glMatrixMode (GL_MODELVIEW);
			glPushMatrix();
				glLoadIdentity (); 
				gluOrtho2D (0, mWorldState->mWindowWidth, 0, mWorldState->mWindowHeight);
				glColor3f(0.5, 1.0, 0.5);

				if( search )
				{
					// Draw heuristic values
					sprintf( string, "%i nodes expanded", search->NumExpandedNodes() + search->GetHeuristic()->NumExpandedNodes() );
					mFont->Render(string);

					// Draw heuristic name
					glTranslatef(0.0f, 40.0f, 0.0f);
					mFont->Render( search->GetHeuristic()->GetName().c_str() );
					glTranslatef(0.0f, -40.0f, 0.0f);
				}
				
				// Draw framerate
				glTranslatef(0.0f, mWorldState->mWindowHeight-40.0f, 0.0f);
				sprintf( string, "%5.1f fps", mWorldState->mFrameRate );
				mFont->Render(string);

			glPopMatrix();
			glMatrixMode (GL_PROJECTION);
		glPopMatrix();
		glMatrixMode( GL_MODELVIEW );
		glEnable(GL_DEPTH_TEST);
	}
}
